////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.series
{

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.filters.DropShadowFilter;
import flash.geom.Rectangle;
import flash.utils.Dictionary;

import mx.charts.HitData;
import mx.charts.chartClasses.CartesianChart;
import mx.charts.chartClasses.CartesianTransform;
import mx.charts.chartClasses.DataDescription;
import mx.charts.chartClasses.IColumn;
import mx.charts.chartClasses.IStackable;
import mx.charts.chartClasses.InstanceCache;
import mx.charts.chartClasses.LegendData;
import mx.charts.chartClasses.Series;
import mx.charts.chartClasses.StackedSeries;
import mx.charts.renderers.BoxItemRenderer;
import mx.charts.series.items.ColumnSeriesItem;
import mx.charts.series.renderData.ColumnSeriesRenderData;
import mx.charts.styles.HaloDefaults;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IViewCursor;
import mx.core.ClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.SolidColor;
import mx.styles.CSSStyleDeclaration;
import mx.styles.ISimpleStyleClient;

use namespace mx_internal;

include "../styles/metadata/FillStrokeStyles.as"
include "../styles/metadata/ItemRendererStyles.as"

/**
 *  Defines a data series for a ColumnChart control. By default, this class uses the IBoxRenderer class.
 *  Optionally, you can define an itemRenderer for the 
 *  data series. The itemRenderer must implement the IBoxRenderer interface. 
 *
 *  @mxml
 *  <p>
 *  The <code>&lt;mx:ColumnSeries&gt;</code> tag inherits all the properties of its parent classes, and 
 *  the following properties:
 *  </p>
 *  <pre>
 *  &lt;mx:ColumnSeries
 *    <strong>Properties</strong>
 *    columnWidthRatio=".65"
 *    legendData="<i>No default</i>"
 *    maxColumnWidth="<i>No default</i>"
 *    minField="null"
 *    offset="<i>No default</i>"
 *    sortOnXField="false|true"
 *    stacker="<i>No default</i>"
 *    stackTotals="<i>No default</i>"
 *    xField="null"
 *    yField="null"
 *    
 *    <strong>Styles</strong>
 *    fill="<i>IFill; no default</i>"
 *    itemRenderer="<i>BoxItemRenderer</i>"
 *    legendMarkerRenderer="<i>Defaults to series's itemRenderer</i>"
 *    stroke="<i>Stroke; no default</i>"
 *  /&gt;
 *  </pre>
 *  </p>
 *  
 *  @see mx.charts.ColumnChart
 *  
 *  @includeExample ../examples/Column_BarChartExample.mxml
 *  
 */
public class ColumnSeries extends Series implements IColumn,IStackable
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */	
	public function ColumnSeries()
	{
		super();

		_instanceCache = new InstanceCache(null,this);
		_instanceCache.creationCallback = applyItemRendererProperties;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private var _instanceCache:InstanceCache;
	
	/**
	 *  @private
	 */
	private var _renderData:ColumnSeriesRenderData;	

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

    //----------------------------------
	//  columnWidthRatio
    //----------------------------------

	/**
	 *  @private
	 */
	private var _columnWidthRatio:Number = 0.65;
	
    [Inspectable(category="General", defaultValue="0.65")]

	/**
	 *  Specifies the width of columns relative to the category width. A value of 1 uses the entire space, while a value of .6 
	 *  uses 60% of the column's available space. 
	 *  You typically do not set this property directly. 
	 *  The actual column width used is the smaller of <code>columnWidthRatio</code> and the <code>maxColumnWidth</code> property.
	 *  
	 *  @default 0.65
	 */
	public function get columnWidthRatio():Number
	{
		return _columnWidthRatio;
	}		
	
	/**
	 *  @private
	 */
	public function set columnWidthRatio(value:Number):void
	{
		_columnWidthRatio = value;

		invalidateTransform();
	}
	
    //----------------------------------
	//  maxColumnWidth
    //----------------------------------

	/**
	 *  @private
	 */
	private var _maxColumnWidth:Number;
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the width of the columns, in pixels. The actual column width used is the smaller 
	 *  of this style and the <code>columnWidthRatio</code> property.
	 *  Clustered columns divide this space proportionally among the columns in each cluster. 
	 */
	public function get maxColumnWidth():Number
	{
		return _maxColumnWidth;
	}
	
	/**
	 *  @private
	 */
	public function set maxColumnWidth(value:Number):void
	{
		_maxColumnWidth = value;

		invalidateTransform();
	}

    //----------------------------------
	//  offset
    //----------------------------------

	/**
	 *  @private
	 */
	private var _offset:Number = 0;

    [Inspectable(category="General", defaultValue="0")]

	/**
	 *  Specifies how far to offset the center of the columns from the center of the available space, relative to the category width. 
	 *  At the value of default 0, the columns are centered on the space.
	 *  Set to -50 to center the column at the beginning of the available space.
	 *  You typically do not set this property directly. The ColumnChart control manages this value based on 
	 *  its <code>columnWidthRatio</code> property.
	 */
	public function get offset():Number
	{
		return _offset;
	}
	
	/**
	 *  @private
	 */
	public function set offset(value:Number):void
	{
		_offset = value;

		invalidateTransform();
	}

    //----------------------------------
	//  yField
    //----------------------------------

	/**
	 *  @private
	 */
	private var _yField:String = "";
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider that determines the y-axis location of the top of a column. 
	 *  If <code>null</code>, the ColumnSeries assumes the data provider is an Array of numbers and uses the numbers as values. 
	 *  The default value is <code>null</code>.
	 */
	public function get yField():String
	{
		return _yField;
	}
	
	/**
	 *  @private
	 */
	public function set yField(value:String):void
	{
		_yField = value;

		dataChanged();
	}

    //----------------------------------
	//  minField
    //----------------------------------

	/**
	 *  @private
	 */
	private var _minField:String = "";
	
	/**
	 *  @private
	 */
	private var _userMinField:String = "";
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider that determines the y-axis location of the bottom of a column. 
	 *  If <code>null</code>, the columns are based at the range minimum (or maximum, if the field value is negative). 
	 *  The default value is <code>null</code>.
	 */
	public function get minField():String
	{
		return _userMinField;
	}
	
	/**
	 *  @private
	 */
	public function set minField(value:String):void
	{
		_userMinField = value;
		_minField = value;

		dataChanged();
	}

    //----------------------------------
	//  stacker
    //----------------------------------

	/**
	 *  @private
	 */
	private var _stacker:StackedSeries;
	
	/**
	 *  @private
	 */
	private var _stacked:Boolean = false;
	
	/**
	 *  The StackedSeries associated with this BarSeries.
	 *  The stacker manages the series's stacking behavior.
	 */
	public function get stacker():StackedSeries
	{
		return _stacker;
	}
	
	/**
	 *  @private
	 */
	public function set stacker(value:StackedSeries):void
	{
		_stacker = value;
	}

    //----------------------------------
	//  xField
    //----------------------------------

	/**
	 *  @private
	 */
	private var _xField:String = "";
	
    [Inspectable(category="General")]

	/**
	 *  Specifies the field of the data provider that determines the x-axis location of the column. 
	 *  If <code>null</code>, Flex renders the columns in the order they appear in the data provider. 
	 *  The default value is <code>null</code>.
	 */
	public function get xField():String
	{
		return _xField;
	}
	
	/**
	 *  @private
	 */
	public function set xField(value:String):void
	{
		_xField = value;

		dataChanged();
	}

    //----------------------------------
	//  sortOnXField
    //----------------------------------

	/**
	 *  @private
	 */
	private var _sortOnXField:Boolean = false;
	
	[Inspectable]

	/** 
	 *  Requests the columns be sorted from left to right before rendering. By default, the 
	 *  ColumnSeries renders columns in the order they appear in the data provider. 
	 *  
	 *  <p>If you use the <code>xField</code> property to determine the position of each column, 
	 *  columns can appear in a different order on the screen. Columns can be rendered in 
	 *  any order. However, some custom columns might rely on the columns being rendered 
	 *  from left to right.</p>
	 *  
	 *  @default false
	 */
	public function get sortOnXField():Boolean
	{
		return _sortOnXField;
	}

	/**
	 *  @private
	 */
	public function set sortOnXField(value:Boolean):void 
	{
		if (_sortOnXField == value)
			return;

		_sortOnXField = value;

		invalidateMapping();
	}

    //----------------------------------
	//  itemType
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartItem used by this series to represent individual items.
	 *  Subclasses can override and return a more specialized class if they need to store additional information in the items
	 */
	protected function get itemType():Class
	{
		return ColumnSeriesItem;
	}
	
    //----------------------------------
	//  renderDataType
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  The subtype of ChartRenderData used by this series to store all data necessary to render.
	 *  Subclasses can override and return a more specialized class if they need to store additional information for rendering.
	 */
	protected function get renderDataType():Class
	{
		return ColumnSeriesRenderData;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @inheritDoc
	 */
	public function stack(stackedXValueDictionary:Dictionary, previousElement:IStackable):Number
	{
		var i:uint = 0;
		var itemClass:Class = itemType;
		var chartItem:ColumnSeriesItem;
		var haveYField:Boolean = (_yField != null && _yField != "");
		var haveXField:Boolean = (_xField != null && _xField != "");
		var maxValue:Number = 0;

		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType();		
		_renderData.cache = [];

		if (cursor != null)
		{
			cursor.seek(CursorBookmark.FIRST);
			while (!cursor.afterLast)
			{
				var dataItem:* = cursor.current;
				_renderData.cache[i] = chartItem = new itemClass(this,dataItem,i);

				var xValue:* = (haveXField)? dataItem[_xField]:i;
				var yValue:* = Number((haveYField)? dataItem[_yField]:dataItem);
				
				var stackedValue:Object = stackedXValueDictionary[xValue];
				if(stackedValue == null)
				{
					stackedValue = 0;
				}
				yValue += stackedValue;
				chartItem.minValue = stackedValue;
				stackedXValueDictionary[xValue] = yValue;
				chartItem.yValue = yValue;
				chartItem.xValue = xValue;
				maxValue = Math.max(maxValue,yValue);
				
				i++;
				cursor.moveNext();				
			}			
		}
		
		_stacked = (previousElement != null);
		invalidateMapping(true);
		invalidateData(false);
		return maxValue;
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateData(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateData(invalid);		
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateMapping(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateMapping(invalid);		
	}

	/**
	 *  The stack totals for the series.
	 *  
	 *  @param totals The totals to set.
	 */
	public function set stackTotals(totals:Dictionary):void
	{
		if (totals != null)
		{
			var cache:Array = _renderData.cache;
			var n:int = _renderData.cache.length;
			for (var i:int = 0; i < n; i++)
			{
				var item:ColumnSeriesItem = cache[i];
				var total:Number = totals[item.xValue];
				item.yValue = Math.min(100, Number(item.yValue) / total * 100);
				item.minValue =
					Math.min(100, Number(item.minValue) / total * 100);
			}
		}
	}

	/**
	 *  @private
	 */
	override protected function updateData():void
	{
		if(_stacker != null)
		{
			_stacker.stack();
			super.updateData();
			return;
		}	
		_stacked = false;

		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType();		
		_renderData.cache = [];

		var i:uint = 0;
		var itemClass:Class = itemType;
		var v:ColumnSeriesItem;
		if (cursor != null)
		{
			cursor.seek(CursorBookmark.FIRST);
			while (!cursor.afterLast)
			{
				_renderData.cache[i] = v = new itemClass(this,cursor.current,i);
				i++;
				cursor.moveNext();
				
			}
			
		}
		
		cacheIndexValues(_xField,_renderData.cache,"xValue");
		cacheDefaultValues(_yField,_renderData.cache,"yValue");
		
		if (_minField != "")		
		{
			cacheNamedValues(_minField,_renderData.cache,"minValue");
			_renderData.renderedBase = NaN;
		}
		super.updateData();
	}

	/**
	 *  @private
	 */
	override protected function updateMapping():void
	{
		dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(_renderData.cache,"xValue","xNumber",(_xField == ""));
		dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(_renderData.cache,"yValue","yNumber");
		if (_minField != "" || _stacked)
		{
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(_renderData.cache,"minValue","minNumber");
		}
		
		// now convert
		if (_xField != "" && _sortOnXField)
			_renderData.cache.sortOn("xNumber",Array.NUMERIC);		
		super.updateMapping();
		
	}

	
	/**
	 *  @private
	 */
	override protected function updateFilter():void
	{
		if (filterData)
		{
			_renderData.filteredCache = _renderData.cache.concat();

			dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(_renderData.filteredCache,"xNumber","xFilter");
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(_renderData.filteredCache,"yNumber","yFilter");
			if (_minField != "" || _stacked)
			{
				dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(_renderData.filteredCache,"minNumber","minFilter");
			}

			stripNaNs(_renderData.filteredCache,"yFilter");
			stripNaNs(_renderData.filteredCache,"xFilter");
			if (_minField != "" || _stacked)
			{
				stripNaNs(_renderData.filteredCache,"minFilter");
			}
		}
		else
		{
			_renderData.filteredCache = _renderData.cache;
		}
		super.updateFilter();
	}
	
	/**
	 *  @private
	 */
	override protected function updateTransform():void
	{
		if (_minField != "" || _stacked)
			dataTransform.transformCache(_renderData.filteredCache,null,null,"minNumber","min");
		else
		{
			var baseVal:Number = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).baseline;
			var stub:Array = [ { yNumber: baseVal } ];
			dataTransform.transformCache(stub,null,null,"yNumber","y");
			var len:uint = _renderData.filteredCache.length;
			_renderData.renderedBase = stub[0].y;
			for (var i:int=0;i<len;i++)
				_renderData.filteredCache[i].min=_renderData.renderedBase;
		}
		dataTransform.transformCache(_renderData.filteredCache,"xNumber","x","yNumber","y");


		var unitSize:Number = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).unitSize;
		
		var params:Array = [{xNumber:0},{xNumber:_columnWidthRatio*unitSize/2},{xNumber:_offset*unitSize}];
		dataTransform.transformCache(params,"xNumber","x",null,null);



		_renderData.renderedHalfWidth = (params[1].x -  params[0].x);
		if (_offset == 0)
		{
			_renderData.renderedXOffset = 0;
		}
		else
		{
			_renderData.renderedXOffset = (params[2].x -  params[0].x);
		}
		
		if (!isNaN(_maxColumnWidth) && _maxColumnWidth < _renderData.renderedHalfWidth)
		{
			_renderData.renderedXOffset *=   _maxColumnWidth/_renderData.renderedHalfWidth;
			_renderData.renderedHalfWidth = _maxColumnWidth;
		}


		super.updateTransform();
	}


	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
			
		super.updateDisplayList(unscaledWidth, unscaledHeight);


		if (!dataTransform)
			return;

		
		var g:Graphics = graphics;
		g.clear();

		if (!isNaN(_maxColumnWidth) && (_maxColumnWidth <= 0 || _columnWidthRatio <= 0))
			return;
		
		var renderData:ColumnSeriesRenderData = (transitionRenderData == null)? _renderData:ColumnSeriesRenderData(transitionRenderData);
		var activeCount:Array = renderData.filteredCache;

		var i:uint;
		var sampleCount:uint = activeCount.length;
		var rc:Rectangle;
		var instances:Array;
		var inst:IFlexDisplayObject;
		var v:ColumnSeriesItem;
		
		_instanceCache.factory = getStyle("itemRenderer");
		_instanceCache.count = sampleCount;			
		instances = _instanceCache.instances;

		var bSetData:Boolean = (sampleCount > 0 && (instances[0] is IDataRenderer))

		if (transitionRenderData!= null && transitionRenderData.elementBounds != null)
		{
			var elementBounds:Array = transitionRenderData.elementBounds;

			for (i=0;i<sampleCount;i++)
			{
				inst = instances[i];
				v = activeCount[i];
				v.itemRenderer = inst;
				if (bSetData)
					(inst as IDataRenderer).data = v;
				rc = elementBounds[i];
				inst.move(rc.left,rc.top);
				inst.setActualSize(rc.width,rc.height);
			}
		}
		else
		{

			
			var ro:Number = renderData.renderedHalfWidth + renderData.renderedXOffset;
			var lo:Number = - renderData.renderedHalfWidth + renderData.renderedXOffset;

			rc = new Rectangle();
			rc.bottom = Number((_minField == "")? - renderData.renderedBase : 0);

			for (i=0;i<sampleCount;i++)
			{
				v = activeCount[i];
				
				rc.left = v.x + lo;
				rc.right = v.x + ro;
				rc.top = v.y;
				if (!isNaN(v.min))
					rc.bottom = v.min;
				else
					rc.bottom = renderData.renderedBase;

				inst = instances[i]
				v.itemRenderer = inst;
				if (bSetData)
					(inst as IDataRenderer).data = v;
				inst.move(rc.left,rc.top);
				inst.setActualSize(rc.width,rc.height);
			}

		}
	}

	/**
	 *  Customizes the item renderer instances that are used to represent the chart. 
	 *  This method is called automatically whenever a new item renderer is needed 
	 *  while the chart is being rendered. 
	 *  You can override this method to add your own customization as necessary.
	 *  
	 *	@param	instance	The new item renderer instance that is being created.
	 *	@param	cache		The InstanceCache that is used to manage the item renderer instances.
	 */
	protected function applyItemRendererProperties(instance:DisplayObject,cache:InstanceCache):void
	{
		if (instance is ISimpleStyleClient)
			ISimpleStyleClient(instance).styleName = this;
	}

	/**
	 *  @private
	 */
	override public function describeData(dimension:String, requiredFields:uint) : Array
	{
		validateData();

		if (_renderData.cache.length == 0)
			return [];

		var description:DataDescription = new DataDescription();
		description.boundedValues = null;

		if (dimension == CartesianTransform.VERTICAL_AXIS)
		{
			extractMinMax(_renderData.cache,"yNumber",description);
			if (_minField != "")
				extractMinMax(_renderData.cache,"minNumber",description);

		}
		else if (dimension == CartesianTransform.HORIZONTAL_AXIS)
		{
			if (_xField != "")
			{
				if (_sortOnXField == false && (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					// if we need to know the min interval, then we rely on the cache being in order. So we need to sort it if it
					// hasn't already been sorted
					var sortedCache:Array = _renderData.cache.concat();
					sortedCache.sortOn("xNumber",Array.NUMERIC);		
					extractMinMax(sortedCache,"xNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
				else
				{
					extractMinMax(_renderData.cache,"xNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
			}
			else 
			{
				description.min = _renderData.cache[0].xNumber;
				description.max = _renderData.cache[_renderData.cache.length-1].xNumber;
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					extractMinInterval(_renderData.cache,"xNumber",description);
				}
			}
			
			description.padding = .5;
		}
		else
			return [];
			
		return [description];
	}

	/**
	 *  @private
	 */
	override public function findDataPoints(x:Number,y:Number,sensitivity:Number):Array
	{
		if (interactive == false)
			return [];
			
			
		
		var minDist:Number = _renderData.renderedHalfWidth + sensitivity;
		var minItem:ColumnSeriesItem;		

		var len:uint = _renderData.filteredCache.length;
		var i:uint;
		var right:Number = unscaledWidth;
		
		for (i=0;i<len;i++)
		{
			var v:ColumnSeriesItem = _renderData.filteredCache[i];
			
			if (v.x + _renderData.renderedXOffset + _renderData.renderedHalfWidth <= 0)
				continue;
			if (v.x + _renderData.renderedXOffset - _renderData.renderedHalfWidth >= right)
				continue;

			var dist:Number = Math.abs((v.x + _renderData.renderedXOffset) - x);
			if (dist > minDist)
				continue;
				
			var base:Number = ((isNaN(v.min))? _renderData.renderedBase : v.min);
			var max:Number = Math.max(v.y,base);
			var min:Number = Math.min(v.y,base);


			if (min - y > sensitivity)
				continue;

			if (y - max > sensitivity)
				continue;

			minDist = dist;
			minItem = v;
			if (dist < _renderData.renderedHalfWidth)
			{
				// we're actually inside the column, so go no further.
				break;
			}
		}

		if (minItem != null)
		{
			var ypos:Number = (isNaN(minItem.min))? minItem.y : Math.min(minItem.y,minItem.min);
			var id:uint = minItem.index;
			var hd:HitData = new HitData(createDataID(id),Math.sqrt(minDist),minItem.x + _renderData.renderedXOffset,ypos,minItem);
			hd.dataTipFunction = formatDataTip;
			return [hd];
		}
		return [];
	}

	/**
	 *  @private
	 */
	private function formatDataTip(hd:HitData):String
	{
		var dt:String = "";
		var n:String = displayName;
		if (n != null && n.length>0)
		dt += "<b>" + n + "</b><BR/>";
		
		var xName:String = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).displayName;
		if (xName != "")
			dt += "<i>" + xName + ":</i> ";
		dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(ColumnSeriesItem(hd.chartItem).xValue) + "\n";


		var yName:String = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).displayName;
		if (_minField == "")
		{
			if (yName != "")
				dt += "<i>" + yName + ":</i> ";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(ColumnSeriesItem(hd.chartItem).yValue) + "\n";
		}
		else
		{
			if (yName != "")
				dt += "<i>" + yName + " (high):</i> ";
			else
				dt += "<i>high:</i> ";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(ColumnSeriesItem(hd.chartItem).yValue) + "\n";

			if (yName != "")
				dt += "<i>" + yName + " (low):</i> ";
			else
				dt += "<i>low:</i> ";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(ColumnSeriesItem(hd.chartItem).minValue) + "\n";
		}
		
		return dt;
	}
	
	/**
	 *  @private
	 */
	override public function get legendData() : Array
	{
		var ld:LegendData = new LegendData();
		var marker:IFlexDisplayObject;
		
		ld.element = this;
		var markerFactory:IFactory = getStyle("legendMarkerRenderer");
		if (markerFactory == null)
			markerFactory = getStyle("itemRenderer");
		if (markerFactory) 
		{
			marker = markerFactory.newInstance();
			if (marker as ISimpleStyleClient)
				(marker as ISimpleStyleClient).styleName = this;
		}
		ld.marker = marker;
		ld.label = displayName;	

		return [ld];
	}

	/**
	 *  @private
	 */
	override public function styleChanged(styleProp : String) : void
	{
		super.styleChanged(styleProp);
		var styles:String = "stroke fill";
		if (styleProp == null || styleProp == "" || styles.indexOf(styleProp) != -1)
		{
			invalidateDisplayList();
			legendDataChanged();
		}
	}

	/**
	 *  @private
	 */
	override protected function get renderData():Object
	{
		if (_renderData == null)
		{
			var renderDataType:Class = this.renderDataType;
			var rv:ColumnSeriesRenderData = new renderDataType();
			rv.cache = rv.filteredCache = [];
			rv.renderedHalfWidth = 0;
			rv.renderedXOffset = 0;
			rv.renderedBase = 0;

			return rv;
		}

		return _renderData;
	}
	
	/**
	 *  @private
	 */
	override public function beginInterpolation(sourceRenderData:Object,destRenderData:Object):Object
	{
		var idata:Object = initializeInterpolationData(
			sourceRenderData.cache, destRenderData.cache,
			{ x: true, y: true, min: true }, itemType,
			{ sourceRenderData: sourceRenderData,
			  destRenderData: destRenderData });

		var interpolationRenderData:ColumnSeriesRenderData = ColumnSeriesRenderData(destRenderData.clone());

		interpolationRenderData.cache = idata.cache;	
		interpolationRenderData.filteredCache = idata.cache;	

		transitionRenderData = interpolationRenderData;
		return idata;
	}
	
	
	/**
	 *  @private
	 */
	override protected function getMissingInterpolationValues(sourceProps:Object,srcCache:Array,destProps:Object,destCache:Array,index:Number,customData:Object):void
	{
		for (var propName:String in sourceProps)
		{
			var src:Number = sourceProps[propName];
			var dst:Number = destProps[propName];

			if (propName == "y" || propName == "min")
			{
				if (isNaN(src))
				{
					src = customData.destRenderData.renderedBase;
				}
				if (isNaN(dst))
				{
					dst = customData.sourceRenderData.renderedBase;
				}
			}
			else if (propName == "x")
			{
				if (isNaN(src))
				{
					src = dst;
				}
				if (isNaN(dst))
				{
					dst = src;
				}
			}
			sourceProps[propName] = src;
			destProps[propName] = dst;
		}		
	}


	/**
	 *  @private
	 */
	override public function getElementBounds(renderData:Object):void
	{
		var cache :Array = renderData.filteredCache;
		var rb :Array = [];
		var sampleCount:uint = cache.length;		

		if (sampleCount)
		{
			var ro:Number = renderData.renderedHalfWidth + renderData.renderedXOffset;
			var lo:Number = - renderData.renderedHalfWidth + renderData.renderedXOffset;
	
			var v:Object = cache[0];
			var maxBounds:Rectangle = new Rectangle(v.x, v.y,0,0);
			for (var i:uint=0;i<sampleCount;i++)
			{
				v = cache[i];
				var top:Number = Math.min(v.y,v.min);
				var b:Rectangle = new Rectangle(v.x+lo, top,ro-lo, Math.max(v.y,v.min) - top);
				maxBounds.left = Math.min(maxBounds.left,b.left);
				maxBounds.top = Math.min(maxBounds.top,b.top);
				maxBounds.right = Math.max(maxBounds.right,b.right);
				maxBounds.bottom = Math.max(maxBounds.bottom,b.bottom);
				rb[i] = b;
			}
		}
		else
		{
			maxBounds = new Rectangle();			
		}
		renderData.elementBounds = rb;
		renderData.bounds =  maxBounds;
	}

	/**
	 *  @private
	 */
	mx_internal function get items():Array
	{
		return _renderData ? _renderData.filteredCache : null;
	}

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var stylesInited:Boolean = initStyles();
		
	/**
	 *  @private
	 */
	private static function initStyles():Boolean
	{
		HaloDefaults.init();

		var columnSeriesStyle:CSSStyleDeclaration =
			HaloDefaults.createSelector("ColumnSeries");		

		columnSeriesStyle.defaultFactory = function():void
		{
			this.fill = new SolidColor(0);
			this.itemRenderer = new ClassFactory(BoxItemRenderer);
			this.stroke = HaloDefaults.emptyStroke;
		}

		return true;
	}
}

}
